<template>
  <div class="home">
    <p>虚拟滚动</p>
    <ul
      class="list"
      @scroll="onScroll"
      :style="{
        height: `${listHeight}px`,
      }"
    >
      <div :style="{ height: `${scrollHeight}px` }">
        <li
          v-for="item in vList"
          :key="item.id"
          :style="{
            height: `${liHeight}px`,
            lineHeight: `${liHeight}px`,
            transform: `translateY(${scrollTop}px)`,
          }"
        >
          <span>{{ item.id + 1 }}</span> {{ item.text }}
        </li>
      </div>
    </ul>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
const Mock = require("mockjs");
interface list {
  id: number;
  text: string;
}

interface data {
  startIndex: Number;
  list: Array<list>;
  liHeight: number;
  vList: Array<list>;
  listHeight: number;
  throttle: boolean;
  scrollTop: number;
}

export default defineComponent({
  name: "Home",
  created(): void {
    console.time();
    const data = Mock.mock({
      "list|10000": [
        {
          "id|+1": 0,
          text: "@sentence(3,5)",
        },
      ],
    });
    this.list = data.list;
    console.timeEnd();
    this.vList = this.list.slice(0, this.limit);
  },

  data(): data {
    return {
      startIndex: 0,
      liHeight: 40,
      list: [],
      vList: [],
      listHeight: 300,
      throttle: false,
      scrollTop: 0,
    };
  },
  methods: {
    onScroll(e: Event): void {
      if (this.throttle) {
        return;
      }
      this.throttle = true;
      requestAnimationFrame(() => {
        this.throttle = false;
      });
      const { liHeight, startIndex } = this;
      const scrollTop = (<HTMLElement>e.target).scrollTop;
      if (scrollTop > this.scrollHeight * this.limit) {
        return;
      }

      const currentIndex = Math.floor(scrollTop / liHeight);
      if (currentIndex !== startIndex) {
        this.scrollTop = scrollTop;
        this.startIndex = currentIndex;
        const endIndex = currentIndex + this.limit;
        this.vList = this.list.slice(currentIndex, endIndex);
      }
    },
  },
  computed: {
    limit(): number {
      return Math.ceil(this.listHeight / this.liHeight);
    },
    scrollHeight(): number {
      return this.list.length * this.liHeight;
    },
  },
});
</script>

<style lang="scss" scoped>
.list {
  width: 600px;
  overflow-y: auto;
  border: 1px solid #e8e8e8;
  li {
    list-style: none;
    margin: 0 20px;
  }
  li:nth-of-type(odd) {
    border-top: 1px solid gainsboro;
    border-bottom: 1px solid gainsboro;
  }
}
</style>
